Skip to content

security(wallet): encrypt MMKV wallet vault with device-bound keychai… - #134

Merged
cybermax4200 merged 1 commit into
ecotask-network:mainfrom
aristotle224:fix/wallet-vault-encryption
Aug 26, 2026
Merged

security(wallet): encrypt MMKV wallet vault with device-bound keychai…#134
cybermax4200 merged 1 commit into
ecotask-network:mainfrom
aristotle224:fix/wallet-vault-encryption

Conversation

@aristotle224

Copy link
Copy Markdown
Contributor

Summary

Closes #122

walletVault was opening a plaintext MMKV instance (new MMKV({ id: 'wallet-vault' })), so every Stellar secret seed was written as cleartext to the device's MMKV flat-file, exposed to rooted devices / ADB pull / backup extraction.

This encrypts the wallet vault with an AES key that is generated per-device and bound to the OS secure enclave (iOS Keychain / Android Keystore via react-native-keychain), and transparently migrates any existing plaintext secrets.

Changes

  • src/services/walletVault.ts:
    • Derive a 32-byte key with crypto.getRandomValues and store it in the secure enclave (WHEN_UNLOCKED_THIS_DEVICE_ONLY, SECURE_HARDWARE), with an ANY/software fallback for Android < API 23.
    • Open MMKV with new MMKV({ id: 'wallet-vault-secure', encryptionKey: key }).
    • initWalletVault() (async, idempotent) loads/creates the key, builds the encrypted store, and runs a one-time migration: copy every secret:* key from the legacy plaintext wallet-vault instance into the encrypted instance → delete from legacy → MMKV.removeMMKV('wallet-vault') removes the plaintext file (guarded by a migration flag).
    • Public saveSecret / getInAppSecret / hasInAppSecret / clearInAppSecret remain synchronous; the secret value is never logged or written to JS state / Zustand.
  • App.tsx — calls initWalletVault() at startup.
  • package.jsonreact-native-keychain added as a pinned dependency.
  • Tests/mocks — root __mocks__/react-native-keychain.js (auto-applied so all suites importing walletVault resolve it); extended src/__tests__/__mocks__/setup.ts MMKV mock (per-instance getAllKeys/clear/removeMMKV identity createJSONStorage); walletVault.test.ts extended with keychain + mock-MMKV migration coverage; useStellarWallet.test.tsx initializes the vault.

Acceptance criteria

  • Wallet vault opens MMKV with a device-bound encryptionKey from the OS secure enclave.
  • react-native-keychain added as a pinned dependency (iOS/Android).
  • One-time migration runs on first launch after update; covered by a Jest unit test (mock keychain + mock MMKV).
  • Plain-instance MMKV file deleted after successful migration.
  • Existing walletVault.test.ts tests still pass with keychain mocked.
  • No secret key value appears in console output, Zustand devtools, or persisted store snapshots.

Verification

  • tsc --noEmit: clean
  • eslint (changed files): 0 errors
  • jest: 387/387 passing

Notes / out of scope

  • Native linking (pod install / Gradle) is the platform build step; the dependency is added and consumed here.
  • Biometric gate before key retrieval and changes to signChallengeXDR/buildPaymentXDR are intentionally out of scope.

…n key

Closes ecotask-network#122

- Derive a 32-byte AES key via crypto.getRandomValues and store it in
  the OS secure enclave (react-native-keychain, WHEN_UNLOCKED_THIS_DEVICE_ONLY).
- Open the wallet vault MMKV instance with that encryptionKey instead of
  plaintext.
- Add a one-time migration that moves existing plaintext secrets from the
  legacy 'wallet-vault' instance into the encrypted 'wallet-vault-secure'
  instance, then deletes the plaintext MMKV file.
- Add react-native-keychain as a pinned dependency.
- Wire initWalletVault() into App startup; public API stays synchronous.
- Extend walletVault tests with keychain + MMKV mocks covering migration.

@cybermax4200 cybermax4200 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

@cybermax4200
cybermax4200 merged commit 86fb4b0 into ecotask-network:main Aug 26, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] walletVault creates an unencrypted MMKV instance — secret keys stored in plaintext on device

2 participants